{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Additive synthesis tutorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This method synthesizes sounds by constructing a complex tone made of a fundamental and several harmonics. The tone is obtained by adding together all these components."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import modules\n",
    "\n",
    "import math\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import IPython.display as ipd\n",
    "from scipy.signal import freqz\n",
    "from scipy.signal import sosfreqz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "The function below can be used to synthesize a complex tone containing a fundamental frequency and a number of its harmonics, all scaled by different amplitudes and added to one another at different phase shifts. The number of components is set to 5 currently, but can be easily changed by passing a value for the argument 'n_comp' when calling the function.\n",
    "<br>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to generate complex tone\n",
    "\n",
    "def generate_complex_tone(f0, fs, amplitudes, phases, duration, n_comp=5):\n",
    "    '''\n",
    "    Arguments:\n",
    "    f0 - fundamental freq\n",
    "    fs - sampling freq\n",
    "    amplitudes, phases - absolute amplitude and phase value for the f0 and the harmonics\n",
    "    \n",
    "    Returns the complex tone signal\n",
    "    '''\n",
    "    assert len(amplitudes) == n_comp, \"Incorrect number of amplitudes provided\"\n",
    "    assert len(phases) == n_comp, \"Incorrect number of phases provided\"\n",
    "    \n",
    "    n=np.arange(0,duration*fs)\n",
    "    x=np.zeros(len(n))\n",
    "    \n",
    "    for i in range(len(amplitudes)):\n",
    "        x = x + amplitudes[i]*np.sin(2*np.pi*(i+1)*(f0/fs)*n + phases[i])\n",
    "    return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "Set the parameters for synthesis - the F0, duration, amplitudes and phases of all the components."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Parameters for synthesis\n",
    "\n",
    "pi = np.pi\n",
    "f0=500                   #in Hz\n",
    "fs=16000                 #in samples/s\n",
    "amplitudes=[1,1,1,1,1]\n",
    "phases=[pi/2,pi/2,pi/2,pi/2,pi/2]\n",
    "duration=1              #in seconds"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "Call the function to generate the tone and plot its waveform and magnitude spectrum. Play around with the values of the amplitudes & phases and see if you notice any changes in the resulting audio and plots. Also observe the effect of changing the fs, f0 and n_fft on the magnitude spectrum."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Change below line to '%matplotlib notebook'  to allow zooming in, moving the plot around, etc.\n",
    "%matplotlib inline \n",
    "\n",
    "# Generate a tone based on above specifications\n",
    "x = generate_complex_tone(f0,fs,amplitudes,phases,duration,n_comp=5)\n",
    "\n",
    "# Calculate the DFT of x\n",
    "n_fft=256\n",
    "x_spec = np.fft.rfft(x,n_fft)\n",
    "\n",
    "t = np.arange(len(x))/fs\n",
    "f = np.arange(len(x_spec))*fs/(2*len(x_spec))\n",
    "\n",
    "# Plot the waveform\n",
    "fig,ax=plt.subplots(1,2,figsize=(15, 3))\n",
    "ax[0].plot(t[:5*fs//f0], x[:5*fs//f0])\n",
    "ax[0].set_title('Waveform'); ax[0].set_xlabel('Time (s)')\n",
    "ax[0].minorticks_on(); ax[0].grid(which='both')\n",
    "\n",
    "# Plot the magnitude spectrum\n",
    "ax[1].plot(f, np.abs(x_spec))\n",
    "ax[1].set_title('Magnitude Spectrum')\n",
    "ax[1].set_xlabel('Frequency (Hz)'); ax[1].set_ylabel('Magnitude')\n",
    "ax[1].minorticks_on(); ax[1].grid(which='both')\n",
    "\n",
    "plt.show()\n",
    "ipd.display(ipd.Audio(data=x, rate=fs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br><br>\n",
    "## A simple example of generating a melody using the synthesis block from above\n",
    "\n",
    "In this task, we will generate a melody by synthesizing the corresponding the notes and setting the durations of each note appropriately. You can use the chart below as a reference for the absolute F0 values of musical notes.\n",
    "\n",
    "| Note | Freq (Hz)   | Note | Freq (Hz)   | Note | Freq (Hz)   |\n",
    "|------|-------------|------|-------------|------|-------------|\n",
    "|   C1 |     131     |   F  |     175     |   A# |     233     |\n",
    "|   C# |     139     |   F# |     185     |   B  |     247     |\n",
    "|   D  |     147     |   G  |     196     |   C2 |     262     |\n",
    "|   D# |     156     |   G# |     208     |\n",
    "|   E  |     165     |   A  |     220     |\n",
    "\n",
    "Following is the note sequence(score) for a popular melody. Each tuple specifies (Note, #beats). Take 1 beat = 400 ms.  \n",
    "\n",
    "(C,1) (C, 1) (D,2) (C,2) (F,2) (E,4);\n",
    "\n",
    "(C,1) (C,1) (D,2) (C,2) (G,2) (F,4);\n",
    "\n",
    "(C,1) (C,1) (C2,2) (A,2) (F,2) (E,2) (D,2);\n",
    "\n",
    "(A#,1) (A#,1) (A,2) (F,2) (G,2) (F,4);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "Convert the above note-freq. chart and score to a python dictionary and list, respectively"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "note_freq = {'C':131, 'C#':139, 'D':147, 'D#':156, \n",
    "         'E':165, 'F':175, 'F#':185, 'G':196, \n",
    "         'G#':208, 'A':220, 'A#':233, 'B':247, 'C2':262}\n",
    "\n",
    "melody = [('C',1), ('C', 1), ('D',2), ('C',2), ('F',2), ('E',4), \n",
    "          ('C',1), ('C',1), ('D',2), ('C',2), ('G',2), ('F',4), \n",
    "          ('C',1), ('C',1), ('C2',2), ('A',2), ('F',2), ('E',2), ('D',2),\n",
    "          ('A#',1), ('A#',1), ('A',2), ('F',2), ('G',2), ('F',4)]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br>\n",
    "Then synthesize the melody using the method from above"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fs = 16000\n",
    "beat_dur = 200e-3\n",
    "\n",
    "synth_song = np.array([])\n",
    "for note in melody:\n",
    "    f0 = note_freq[note[0]]\n",
    "    duration = note[1]*beat_dur\n",
    "    \n",
    "    # synthesize note using a complex tone\n",
    "    x = generate_complex_tone(f0,fs,[1,1,1,1,1],[0,0,0,0,0],duration)\n",
    "    \n",
    "    # append to synth_song array:\n",
    "    synth_song = np.append(synth_song, x)\n",
    "    \n",
    "ipd.display(ipd.Audio(data=synth_song, rate=fs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br><b>Acknowledgement</b>: This notebook was created by Rohit M A and Kamini Sabu, graduate students at DAP Lab, Dept of EE, IIT Bombay.<br>\n",
    "Please contact Prof. Preeti Rao at prao@ee.iitb.ac.in in case of any queries."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
